/**
 * Licensed under the Apache License Version 2.0.
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package net.interknit.knitter.cg;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.interknit.knitter.cg.cfg.CGConfig;
import net.interknit.knitter.cg.cfg.CGConfigRootEl;
import net.interknit.knitter.cg.layout.LayoutWriter;
import net.interknit.knitter.cg.model.JavaTypeScanner;
import net.interknit.knitter.cg.template.Template;
import net.interknit.knitter.cg.template.TemplateBuilder;
import net.interknit.knitter.cg.template.TemplateWriter;
import net.interknit.knitter.cg.util.ClassLoader;
import net.interknit.knitter.cg.util.Utils;

import org.apache.tools.ant.Project;

/**
 *
 */
public class CGMain
{
	public static int LOG_PRIORITY_DEBUG = Project.MSG_DEBUG;
	public static int LOG_PRIORITY_LOW = Project.MSG_VERBOSE;
	public static int LOG_PRIORITY_INFO = Project.MSG_INFO;
	public static int LOG_PRIORITY_WARN = Project.MSG_WARN;
	public static int LOG_PRIORITY_ERROR = Project.MSG_ERR;

	private final String cgconfigPath;
	private final String basePackageName;
	private final String outputDir;
	private final String authorName;
	private final boolean useQualifiedTypeNames;
	private final int logPriority;
	private final String[] excludedTypes;
	private final String[] ignoredTypes;

	private final Project antProject;

	private ClassLoader classLoader;
	private CGConfig cgconfig;

	private CGMain(
			String cgconfigPath,
			String basePackageName,
			String outputDir,
			String authorName,
			boolean useQualifiedTypeNames,
			String excludedTypes,
			String ignoredTypes,
			int logPriority,
			Project antProject)
	{
		this.cgconfigPath = cgconfigPath;
		this.outputDir = outputDir;
		this.basePackageName = basePackageName;
		this.authorName = authorName;
		this.useQualifiedTypeNames = useQualifiedTypeNames;
		this.excludedTypes = Utils.delimitedStringToStringArray(excludedTypes);
		this.ignoredTypes = Utils.delimitedStringToStringArray(ignoredTypes);
		this.logPriority = logPriority;

		this.antProject = antProject;

		log(
				"Code Generator arguments:" + "\n" +
				"  " + "cgconfigPath          = " + cgconfigPath + "\n" +
				"  " + "outputDir             = " + outputDir + "\n" +
				"  " + "basePackageName       = " + basePackageName + "\n" +
				"  " + "authorName            = " + authorName + "\n" +
				"  " + "useQualifiedTypeNames = " + useQualifiedTypeNames + "\n" +
				"  " + "excludedTypes         = " + excludedTypes + "\n" +
				"  " + "ignoredTypes          = " + ignoredTypes + "\n" +
				"  " + "logPriority           = " + logPriority,
				LOG_PRIORITY_ERROR
		);
	}

	public String getCGConfigPath()
	{
		return cgconfigPath;
	}

	public String getOutputDir()
	{
		return outputDir;
	}

	public boolean useQualifiedTypeNames()
	{
		return this.useQualifiedTypeNames;
	}

	public int getLogPriority()
	{
		return logPriority;
	}

	public List<String> getExcludedTypes()
	{
		return
				excludedTypes != null ?
				Arrays.asList(excludedTypes) :
				new ArrayList<String>();
	}

	public List<String> getIgnoredTypes()
	{
		return
				ignoredTypes != null ?
				Arrays.asList(ignoredTypes) :
				new ArrayList<String>();
	}

	public Project getAntProject()
	{
		return antProject;
	}

	public CGConfig getCGConfig()
	{
		return cgconfig;
	}

	public ClassLoader getClassLoader()
	{
		return classLoader;
	}

	public void log(String msg)
	{
		if(this.logPriority >= LOG_PRIORITY_INFO)
		{
			if(antProject != null)
				antProject.log("[KNITTER] " + msg);
			else
				System.out.println("[KNITTER] " + msg);
		}
	}

	public void log(Throwable t)
	{
		if(this.logPriority >= LOG_PRIORITY_INFO)
		{
			log(t, Project.MSG_WARN);
		}
	}

	public void log(String msg, int messagePriority)
	{
		if(this.logPriority >= messagePriority)
		{
			if(antProject != null)
				antProject.log("[KNITTER] " + msg, messagePriority);
			else
				System.out.println("[KNITTER] " + msg);
		}
	}

	public void log(String msg, Throwable t, int messagePriority)
	{
		if(this.logPriority >= messagePriority)
		{
			if(antProject != null)
				antProject.log("[KNITTER] " + msg, t, messagePriority);
			else
			{
				System.err.println("[KNITTER] " + msg);
				t.printStackTrace();
			}
		}
	}

	public void log(Throwable t, int messagePriority)
	{
		if(this.logPriority >= messagePriority)
		{
			if(antProject != null)
				antProject.log("[KNITTER] " + t.getMessage(), t, messagePriority);
			else
				t.printStackTrace();
		}
	}

	public void generateCode()
	{
		try
		{
			cgconfig = new CGConfig();

			cgconfig.build(cgconfigPath, basePackageName, outputDir, authorName);

			classLoader = new ClassLoader(cgconfig.getClasspathEntries());

			for(String targetClassName : cgconfig.getTargetClassNames())
			{
				Class<?> clazz = classLoader.loadClass(targetClassName);

				JavaTypeScanner scanner = new JavaTypeScanner(clazz).scan();

				List<Template> templates = TemplateBuilder.build(scanner);

				for(Template template : templates)
				{
					new TemplateWriter(template).write();
				}

				new LayoutWriter(templates).write();

				TemplateBuilder.clear();
			}
		}
		catch(Exception e)
		{
			if(e instanceof RuntimeException)
				throw (RuntimeException) e;
			else
				throw new RuntimeException(e);
		}
	}

	private static CGMain instance;

	public static CGMain getInstance()
	{
		return instance;
	}

	public static String usage()
	{
		return "Usage: java " + CGMain.class.getName() + " " +
				"-p <path to code generation configuration xml> " +
				"[-b <base package name of generated code>] " +
				"[-d <output directory for generated code>] " +
				"[-a <author name>] " +
				"[-q <boolean use fully qualified names for generated types>] " +
				"[-v <verbosity level: 0 to 4. 0 is silent, 4 is verbose] ";
	}

	/**
	 * Static method to start the CGMain (programmatic alternative for main()
	 * method)
	 *
	 * @param p
	 *            Path to the code-generation configuration XML (required)
	 * @param b
	 *            Base package name (optional)
	 * @param d
	 *            Directory for the generated source (optional)
	 * @param a
	 *            Author name (optional)
	 * @param q
	 *            Use fully qualified (long) names for generated types
	 *            (optional)
	 * @param e
	 *            excludedTypes value as defined in CGMain (optional, defaults to null)
	 * @param i
	 *            ignoredTypes value as defined in CGMain (optional, defaults to null)
	 * @param logPriority
	 *            logPriority value as defined in CGMain (optional, defaults to LOG_PRIORITY_WARNING)
	 * @param antProject
	 *            If CGMain.run() is called from {@link CodeGenTask}, antProject
	 *            will be the reference to the current Ant project (optional)
	 */
	static void run(
			String p, String b, String d, String a, boolean q,
			String e, String i, int logPriority, Project antProject)
	{
		instance = new CGMain(p, b, d, a, q, e, i, logPriority, antProject);

		instance.log("Starting code generation...", LOG_PRIORITY_ERROR);
		instance.generateCode();
		instance.log("Code generation completed, see " + d, LOG_PRIORITY_ERROR);
	}

	public static void main(String[] args)
	{
		String p = "cgconfig.xml";
		String d = CGConfigRootEl.DEFAULT_GENERATED_SRC_DIR;
		String b = CGConfigRootEl.DEFAULT_ROOT_PACKAGE_NAME;
		String a = CGConfigRootEl.DEFAULT_AUTHOR;
		String e = null;
		String i = null;
		String q = null;
		int v = LOG_PRIORITY_WARN;

		try
		{
			for(int ai = 0; args != null && ai < args.length - 1; ai++)
			{
				if(args[ai].startsWith("-p")) p = args[ai + 1].trim();
				if(args[ai].startsWith("-d")) d = args[ai + 1].trim();
				if(args[ai].startsWith("-b")) b = args[ai + 1].trim();
				if(args[ai].startsWith("-a")) a = args[ai + 1].trim();
				if(args[ai].startsWith("-q")) q = args[ai + 1].trim();
				if(args[ai].startsWith("-e")) e = args[ai + 1].trim();
				if(args[ai].startsWith("-i")) i = args[ai + 1].trim();
				if(args[ai].startsWith("-v"))
				{
					try
					{
						v = new Integer(args[ai + 1].trim()).intValue();
					}
					catch(Exception numFormat)
					{
						v = new Boolean(args[ai + 1].trim()) ? LOG_PRIORITY_DEBUG : LOG_PRIORITY_WARN;
					}
				}
			}

			run(p, b, d, a, new Boolean(q), e, i, v, null);
		}
		catch(Exception ex)
		{
			System.err.println("\n");

			if(v <= LOG_PRIORITY_WARN) ex.printStackTrace();
			else System.err.println("\t" + ex.getMessage());

			System.err.println("\n");

			System.out.println(usage());
		}
	}
}
